package com.kgajowy.envs;

import com.kgajowy.objects.ComplexObject;
import com.kgajowy.objects.DBID;
import com.kgajowy.objects.ReferenceObject;
import com.kgajowy.objects.SBAObject;
import com.kgajowy.objects.SBAStore;
import com.kgajowy.result.BagResult;
import com.kgajowy.result.BinderResult;
import com.kgajowy.result.ReferenceResult;
import com.kgajowy.result.SingleResult;
import com.kgajowy.result.StructResult;

import edu.pjwstk.jps.datastore.IOID;
import edu.pjwstk.jps.datastore.ISBAStore;
import edu.pjwstk.jps.interpreter.envs.IENVS;
import edu.pjwstk.jps.interpreter.envs.IENVSBinder;
import edu.pjwstk.jps.interpreter.envs.IENVSFrame;
import edu.pjwstk.jps.result.IAbstractQueryResult;
import edu.pjwstk.jps.result.IBagResult;
import edu.pjwstk.jps.result.ISingleResult;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;

public class ENVS implements IENVS {
    
    private SBAStore store;
    
    @SuppressWarnings("unused")
    private IOID rootOID;
    
    private LinkedList<IENVSFrame> stack;

    @Override
    public void init(IOID rootOID, ISBAStore store) {
        this.rootOID = rootOID;
        this.store = (SBAStore)store;
        this.stack = new LinkedList<IENVSFrame>();
        buildBaseStack();
    }

    private void buildBaseStack() {
        ENVSFrame baseFrame;
        LinkedList<IENVSBinder> baseList = new LinkedList<IENVSBinder>();
        
        for (@SuppressWarnings("rawtypes") Entry<DBID, SBAObject> entry : store.getElements().entrySet() ){
            baseList.add(new 
                    ENVSBinder(
                            entry.getValue().getName(), 
                            new ReferenceResult(entry.getValue().getOID())
                            )
            );
        }
        
        baseFrame = new ENVSFrame(baseList);
        stack.add(baseFrame);
        
    }

    @Override
    public IENVSFrame pop() {
            return stack.pollLast();
    }

    @Override
    public void push(IENVSFrame frame) {
        this.stack.add(frame);
    }

    @Override
    public IBagResult bind(String name) {
        IENVSFrame temp = null;
        BagResult bag = null;
        boolean hit = false;
        ArrayList<SingleResult> itemsInFrameWithName = new ArrayList<SingleResult>();
        Iterator<IENVSFrame> iter = stack.descendingIterator();
        while ( iter.hasNext() ){
            temp = iter.next();
            for ( IENVSBinder item : temp.getElements() ) {
                //System.out.println("Compare " + item.getName() + " with " + name);
                if ( item.getName().equals(name) ) {
                    itemsInFrameWithName.add((SingleResult) item.getValue());
                    hit = true;
                    //System.out.println(item.getValue());
                }
            }
            if ( hit ) { 
                bag = new BagResult(itemsInFrameWithName);
                break; 
            }
        }
        
        return bag;
    }

    @Override
    public IENVSFrame nested(IAbstractQueryResult result, ISBAStore store) {
        LinkedList<IENVSBinder> list = new LinkedList<IENVSBinder>();

        if ( result instanceof ReferenceResult ) {
            SBAObject<?> obj = (SBAObject<?>)store.retrieve(((ReferenceResult) result).getOIDValue());
            if ( obj instanceof ComplexObject ) {
                //1. Dla referencji do obiektu złożonego: zbiór zawierający bindery z nazwami i
                //identyfikatorami (ReferenceResult) podobiektów tego obiektu
                
                for ( IOID ioid : ((ComplexObject) obj).getChildOIDs()) {
                    SBAObject<?> objInner = (SBAObject<?>)store.retrieve(ioid);
                    list.add(new ENVSBinder(
                                objInner.getName(),
                                new ReferenceResult(objInner.getOID())
                            ));
                }
                
            } else if ( obj instanceof ReferenceObject ) {
                //2. Dla referencji do obiektu referencyjnego: zbiór zawierający binder z nazwą i
                //identyfikatorem (ReferenceResult) obiektu wskazywanego
                ENVSBinder binder = new ENVSBinder(
                        store.retrieve(obj.getOID()).getName(),
                        new ReferenceResult(obj.getOID())
                        );
                list.add(binder);
            } else if ( SBAStore.isSimpleType(obj) ) {
                //6. Dla referencji do obiektu prostego: pusty zbiór
                // nothing
            }
        } else if ( result instanceof BinderResult ) {
            //3. Dla bindera: ten sam binder
            BinderResult obj = (BinderResult) result;
            ENVSBinder binder = new ENVSBinder(
                    obj.getName(),
                    obj
                    );
            list.add(binder);
        } else if ( result instanceof StructResult ) {
            //4. Dla struktury: zbiór będący sumą operacji nested wszystkich elementów struktury
            StructResult obj = (StructResult) result;
            for (ISingleResult single : obj.elements()) {
                SingleResult item = (SingleResult) single;
                ENVSFrame innerFrame = (ENVSFrame) nested(item, store);
                list.addAll(innerFrame.getElements());
            }
        } else if ( SBAStore.isSimpleType(result) ) {
            //5. Dla wartości atomowej: pusty zbiór
            // nothing
        } else {
            //7. Dla pozostałych rezultatów: pusty zbiór
            // nothing
        }
        
        return new ENVSFrame(list);
    }

}
